/**************************************************************************(C)*
 *
 *  This file is part of the GreenHills project
 *  Copyright (C) 2009  Tieto Corporation
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  Not for commmercial use. Only with Tieto permission.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************(C)*/

#include "../proxy.h"

#include "event/loginevent.h"
#include "event/logoutevent.h"
#include "event/createroomevent.h"
#include "event/joinroomevent.h"
#include "event/leaveroomevent.h"
#include "event/additemevent.h"
#include "event/removeitemevent.h"
#include "event/updateobjectevent.h"
#include "event/identificationevent.h"

#include "event/synchronouschannel.h"
#include "event/asynchronouschannel.h"

#include "net/msgclientsocketconnection.h"
#include "server/defs.h"

#include "util/exception.h"

#include <time.h>
#include <string>
#include <iostream>

#include <QtCore/QTimer>

using namespace greenhills::client::core;
using namespace greenhills::core;
using namespace greenhills::event;
using namespace greenhills::util;
using namespace greenhills::net;
using namespace io;

CProxy *CProxy::instance()
{
    static CProxy *proxyInstance = new CProxy(0);
    return proxyInstance;
}

CProxy::CProxy(QObject *pParent)
    :QObject(pParent),
     mControlConnection(new CMsgClientSocketConnection()),
     mEventConnection(new CMsgClientSocketConnection()),
     mControlChannel(new CSynchronousChannel(this, mControlConnection)),
     mEventChannel(new CAsynchronousChannel(this, mEventConnection))
{
    QObject::connect(mEventChannel,
        SIGNAL(eventReceived(greenhills::event::CEvent)),
        this, SLOT(onEventReceived(greenhills::event::CEvent)));

    QTimer *timer = new QTimer(this);
    timer->setInterval(100);

    QObject::connect(timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
    timer->start();
}

bool CProxy::connect(const std::string &pHost)
{
    try{
        mControlConnection->open(pHost.c_str(), SERVER_PORT_SYNC);
        mEventConnection->open(pHost.c_str(), SERVER_PORT_ASYNC);
    }
    catch(CException &e){
        disconnect();
        return false;
    }

    return true;
}

void CProxy::disconnect()
{
    try{
        mControlConnection->close();
    }
    catch(CException &e) {
    }

    try{
        mEventConnection->close();
    }
    catch(CException &e) {
    }
}

bool CProxy::isConnected()
{
    return mControlConnection->isOpened() && mEventConnection->isOpened();
}

CClientId CProxy::login(const std::string &pUsername,
        const std::string &pPassword)
{
    CLoginRequestEvent loginRequest(pUsername, pPassword);
    CActionEvent *response = mControlChannel->sendMessage(loginRequest);
    if (!response)
        return false;

    Q_ASSERT(loginRequest.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::LoginResponse);

    CLoginResponseEvent* loginResponse =
            dynamic_cast<CLoginResponseEvent*>(response);

    CClientId clientId = loginResponse->clientId();
    delete response;

    CIdentificationEvent identificationEvent(clientId);
    mEventChannel->sendMessage(identificationEvent);

    return clientId;
}

bool CProxy::logout(const CClientId &pClientId)
{
    Q_UNUSED(pClientId);

    CLogoutRequestEvent logoutRequest(pClientId);
    CActionEvent *response = mControlChannel->sendMessage(logoutRequest);
    if (!response)
        return false;

    Q_ASSERT(logoutRequest.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::LogoutResponse);

    CLogoutResponseEvent* logoutResponse =
            dynamic_cast<CLogoutResponseEvent*>(response);

    bool rValue = logoutResponse->succeeded();
    delete response;

    return rValue;
}

CRoomId CProxy::createRoom(const CClientId &pClientId,
        const std::string &pRoomName, const CRoomType &pRoomType,
        const std::string &pPassword)
{
    Q_UNUSED(pClientId);

    CCreateRoomRequestEvent createRoomRequest(pRoomName, pRoomType, pPassword);
    CActionEvent *response = mControlChannel->sendMessage(createRoomRequest);
    if (!response)
        return false;

    Q_ASSERT(createRoomRequest.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::CreateRoomResponse);

    CCreateRoomResponseEvent* createRoomResponse =
            dynamic_cast<CCreateRoomResponseEvent*>(response);

    CRoomId rValue = createRoomResponse->roomId();
    delete response;

    return rValue;
}

bool CProxy::joinRoom(const CClientId &pClientId, const CRoomId &pRoomId,
        const std::string &pPassword)
{
    Q_UNUSED(pClientId);

    CJoinRoomRequestEvent joinRoomRequest(pRoomId, pPassword);
    CActionEvent *response = mControlChannel->sendMessage(joinRoomRequest);
    if (!response)
        return 0;

    Q_ASSERT(joinRoomRequest.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::JoinRoomResponse);

    CJoinRoomResponseEvent* joinRoomResponse =
            dynamic_cast<CJoinRoomResponseEvent*>(response);

    bool rValue = joinRoomResponse->succeeded();
    delete response;

    return rValue;
}

bool CProxy::leaveRoom(const CClientId &pClientId, const CRoomId &pRoomId)
{
    Q_UNUSED(pClientId);

    CLeaveRoomRequestEvent leaveRoomRequest(pRoomId);
    CActionEvent *response = mControlChannel->sendMessage(leaveRoomRequest);
    if (!response)
        return false;

    Q_ASSERT(leaveRoomRequest.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::LeaveRoomResponse);

    CLeaveRoomResponseEvent* leaveRoomResponse =
            dynamic_cast<CLeaveRoomResponseEvent*>(response);

    bool rValue = leaveRoomResponse->succeeded();
    delete response;

    return rValue;
}

CItemId CProxy::addItem(const CClientId &pClientId,
        const CRoomId &pRoomId, const std::string &pItemName,
        const CItemType &pItemType)
{
    Q_UNUSED(pClientId);

    CAddItemRequestEvent addItemRequest(pRoomId, pItemName, pItemType);
    CActionEvent *response = mControlChannel->sendMessage(addItemRequest);
    if (!response)
        return false;

    Q_ASSERT(addItemRequest.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::AddObjectResponse);

    CAddItemResponseEvent* addItemResponse =
            dynamic_cast<CAddItemResponseEvent*>(response);

    CItemId itemId = addItemResponse->itemId();
    delete response;

    return itemId;
}

bool CProxy::removeItem(const CClientId &pClientId,
        const CRoomId &pRoomId, const CItemId &pItemId)
{
    Q_UNUSED(pClientId);

    CRemoveItemRequestEvent removeItemRequest(pRoomId, pItemId);
    CActionEvent *response = mControlChannel->sendMessage(removeItemRequest);
    if (!response)
        return false;

    Q_ASSERT(removeItemRequest.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::RemoveObjectResponse);

    CRemoveItemResponseEvent* removeItemResponse =
            dynamic_cast<CRemoveItemResponseEvent*>(response);

    bool succeeded = removeItemResponse->suceeded();
    delete response;

    return succeeded;
}

bool CProxy::updateItem(const CClientId &pClientId,
        const CRoomId &pRoomId, const CItemId &pItemId,
        const unsigned char *pData, unsigned int pLength)
{
    Q_UNUSED(pClientId);

    CUpdateObjectRequestEvent request(pRoomId, pItemId, pData, pLength);
    CActionEvent *response = mControlChannel->sendMessage(request);
    if (!response)
        return false;

    Q_ASSERT(request.transactionId() == response->transactionId());
    Q_ASSERT(response->type() == CEvent::UpdateObjectResponse);

    CUpdateObjectResponseEvent* updateObjectResponse =
            dynamic_cast<CUpdateObjectResponseEvent*>(response);

    bool rValue = updateObjectResponse->succeeded();
    delete response;

    return rValue;
}

void CProxy::onEventReceived(const CEvent &pEvent)
{
    switch(pEvent.type())
    {
    case CEvent::ObjectAddedEvent:
    {
        const CItemAddedEvent *itemAddedEvent =
                dynamic_cast<const CItemAddedEvent*>(&pEvent);
        Q_ASSERT(itemAddedEvent != 0);

        std::vector<IServerListener*>::iterator it;
        for (it = mListeners.begin(); it != mListeners.end(); it++)
            (*it)->itemAdded(itemAddedEvent->roomId(),
                             itemAddedEvent->itemId(),
                             itemAddedEvent->itemName(),
                             itemAddedEvent->itemType());
        break;
    }
    case CEvent::ObjectUpdatedEvent:
    {
        const CItemUpdatedEvent *itemUpdatedEvent =
                dynamic_cast<const CItemUpdatedEvent*>(&pEvent);
        Q_ASSERT(itemUpdatedEvent != 0);

        std::vector<IServerListener*>::iterator it;
        for (it = mListeners.begin(); it != mListeners.end(); it++)
            (*it)->itemUpdated(itemUpdatedEvent->roomId(),
                             itemUpdatedEvent->itemId(),
                             itemUpdatedEvent->data(),
                             itemUpdatedEvent->length());
        break;
    }
    case CEvent::ObjectRemovedEvent:
    {
        const CItemRemovedEvent *itemRemovedEvent =
                dynamic_cast<const CItemRemovedEvent*>(&pEvent);
        Q_ASSERT(itemRemovedEvent != 0);

        std::vector<IServerListener*>::iterator it;
        for (it = mListeners.begin(); it != mListeners.end(); it++)
            (*it)->itemRemoved(itemRemovedEvent->roomId(),
                               itemRemovedEvent->itemId());
        break;
    }
    default:
        std::cout << "Unknown/Unsupported message type : " <<
                pEvent.type() << std::endl;
    }
}

void CProxy::onTimeout()
{
    if (!isConnected())
        return;

    try{
        mEventConnection->process();
    }
    catch(CException &e){
        disconnect();
    }
}
